/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#include "playerpiano.hh"

#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <memory>
#include <set>

namespace
{
  using namespace playerpiano;

  template<typename Element>
  unsigned int
  argmax (const std::vector<Element>& v)
    {
      unsigned int rv = 0;

      for (unsigned int i = 1; i < v.size (); ++i)
        { 
          if (v[i] > v[rv])
            { 
              rv = i;
            }
        }
    
      return rv;
    }

  void
  safe_ratio (std::stringstream& rv,
              float              numerator,
              unsigned long long denominator)
    {
      if (denominator > 0)
        {
          rv << (numerator / denominator);
        }
      else
        {
          rv << "n/a";
        }
    }

  template<typename PRating,
           typename Distribution,
           typename Classifier>
  void
  read_loop (FILE*               in,
             uint64_t            t,
             float               eta_unscaled,
             float               rho,
             unsigned int        n_workers,
             PRating&            prating,
             unsigned int        n_items,
             unsigned int        n_labels,
             Distribution&       priorlogbeta,
             unsigned int        n_features,
             Classifier&         classifier,
             bool                test_only,
             FILE*               predict,
             bool                quiet)
    {
      IndexedFeatureSet feature_set;
      std::vector<Rating> ratings;
      static const int max_tag = 1024;
      char tag[max_tag];

      double since_last_q = 0;
      double since_last_kl = 0;
      double total_q = 0;
      double total_kl = 0;
      unsigned long long bad_lines_since_last = 0;
      unsigned long long example_count = 0;
      unsigned long long labeled_example_count = 0;
      unsigned long long rating_count = 0;
      unsigned long long last_bad_lineno = 0;
      unsigned long long output_number = 0;
      unsigned long long since_last_example_count = 0;
      unsigned long long since_last_labeled_example_count = 0;
      unsigned long long since_last_rating_count = 0;

      std::vector<float> clamp_logp;
      std::vector<float> pz (n_labels);
      std::vector<uint64_t> last_t (n_workers, t);
      std::vector<uint64_t> last_feature_t (n_features, t);

      float eta = eta_unscaled * fastpow (t, rho);
      float log_n_labels_m1 = fastlog (n_labels - 1);
      static const float logepsilon = -11.512925464970228;
      static const float log1mepsilon = -0.000010000050000333336;

      if (! quiet)
        {
          fprintf (stderr, 
                   "cumul    since    cumul    since      example current current current  current\n"
                   "avg q    last     avg ce   last       counter   label predict ratings features\n");
        }

      int clamp = -1;

      for (unsigned long long lineno = 0; ! feof (in); ++lineno)
        {
          float importance_weight;

          // TODO: actually use the importance weight (!)
          // need to replace (uint64_t t) with (double t) everywhere

          if (! parse_input_line (in, 
                                  ratings,
                                  feature_set,
                                  importance_weight,
                                  tag,
                                  max_tag,
                                  clamp,
                                  n_labels,
                                  n_workers))
            {
              Feature constant_feature = { 0, 1 };
              feature_set[0].push_back (constant_feature);

              if (! test_only)
                {
                  for (IndexedFeatureSetIterator it = begin (feature_set);
                       it != end (feature_set);
                       ++it)
                    {
                      unsigned int f = (*it).feature % n_features;

                      classifier.apply_deferred_prior (f,
                                                       last_feature_t[f],
                                                       t,
                                                       eta,
                                                       rho);

                      last_feature_t[f] = t;
                    }
                }

              const std::vector<float>& classifier_logp = 
                classifier.logp (feature_set);

              if (clamp >= 0)
                {
                  clamp_logp.resize (n_labels, logepsilon - log_n_labels_m1);
                  clamp_logp[clamp] = log1mepsilon;
                }

              const std::vector<float>& logp (clamp >= 0 ? clamp_logp
                                                         : classifier_logp);

              float logbeta;
              float q = blockopt (t,
                                  eta,
                                  rho,
                                  last_t,
                                  prating,
                                  n_items,
                                  logbeta,
                                  pz,
                                  logp,
                                  ratings,
                                  priorlogbeta,
                                  test_only);

              float kl = kldivergence (classifier_logp, pz);

              if (! test_only) 
                {
                  classifier.update (t, 
                                     eta,
                                     rho,
                                     feature_set,
                                     classifier_logp,
                                     pz);
                  ++t; 
                }
    
              if (predict)
                { 
                  fprintf (predict, "%s\t%f", tag, logbeta);
                  for (unsigned int i = 0; i < n_labels; ++i)
                    { 
                      fprintf (predict, "\t%f", pz[i]);
                    }
                  fprintf (predict, "\n");
                }
    
              since_last_rating_count += ratings.size ();

              if (ratings.size () > 0)
                {
                  since_last_q += q;
                }

              if (ratings.size () > 0 || clamp >= 0)
                {
                  since_last_kl += kl;
                  ++since_last_labeled_example_count;
                }

              ++since_last_example_count;
            }
          else if (! feof (in))
            {
              ++bad_lines_since_last;
              last_bad_lineno = lineno;
            }

          if (since_last_example_count > (1ULL << output_number))
            {
              example_count += since_last_example_count;
              labeled_example_count += since_last_labeled_example_count;
              rating_count += since_last_rating_count;
              total_q += since_last_q;
              total_kl += since_last_kl;
    
              if (! quiet)
                {
                  std::stringstream cumulq;
                  safe_ratio (cumulq, total_q, rating_count);
                  std::stringstream slq;
                  safe_ratio (slq, since_last_q, since_last_rating_count);
                  std::stringstream cumulkl;
                  safe_ratio (cumulkl, total_kl, labeled_example_count);
                  std::stringstream slkl;
                  safe_ratio (slkl,
                              since_last_kl,
                              since_last_labeled_example_count);

                  fprintf (stderr, 
                           "%-8.8s %-8.8s %-8.8s %-8.8s %9llu %7d %7u %7zu %8zu\n",
                           cumulq.str ().c_str (),
                           slq.str ().c_str (),
                           cumulkl.str ().c_str (),
                           slkl.str ().c_str (),
                           example_count,
                           clamp,
                           argmax (pz),
                           ratings.size (),
                           feature_set.total_size ());
                }
    
              if (bad_lines_since_last)
                {
                  if (! quiet)
                    {
                      fprintf (stderr,
                               "warning: encountered %llu malformed input "
                               "lines, most recent at line %llu\n",
                               bad_lines_since_last,
                               last_bad_lineno);
                    }
    
                  bad_lines_since_last = 0;
                  last_bad_lineno = 0;
                }
    
              since_last_example_count = 0;
              since_last_labeled_example_count = 0;
              since_last_rating_count = 0;
              since_last_q = 0;
              since_last_kl = 0;
              ++output_number;
            }
        }

     if (since_last_example_count > 0)
       { 
         example_count += since_last_example_count;
         labeled_example_count += since_last_labeled_example_count;
         rating_count += since_last_rating_count;
         total_q += since_last_q;
         total_kl += since_last_kl;
   
         if (! quiet)
           {
             std::stringstream cumulq;
             safe_ratio (cumulq, total_q, rating_count);
             std::stringstream slq;
             safe_ratio (slq, since_last_q, since_last_rating_count);
             std::stringstream cumulkl;
             safe_ratio (cumulkl, total_kl, labeled_example_count);
             std::stringstream slkl;
             safe_ratio (slkl,
                         since_last_kl,
                         since_last_labeled_example_count);

             fprintf (stderr, 
                      "%-8.8s %-8.8s %-8.8s %-8.8s %9llu %7d %7u %7zu %8zu\n",
                      cumulq.str ().c_str (),
                      slq.str ().c_str (),
                      cumulkl.str ().c_str (),
                      slkl.str ().c_str (),
                      example_count,
                      clamp,
                      argmax (pz),
                      ratings.size (),
                      feature_set.total_size ());
           }
   
         if (bad_lines_since_last)
           {
             fprintf (stderr,
                      "error: encountered %llu malformed input lines, "
                      "most recent at line %llu\n",
                      bad_lines_since_last,
                      last_bad_lineno);
   
             bad_lines_since_last = 0;
             last_bad_lineno = 0;
           }
   
         since_last_example_count = 0;
         since_last_labeled_example_count = 0;
         since_last_rating_count = 0;
         since_last_q = 0;
         since_last_kl = 0;
       }

      if (! test_only)
        { 
          if (! quiet)
            {
              fprintf (stderr, "applying deferred prior updates ...");
            }

          for (unsigned int w = 0; w < n_workers; ++w)
            {
             prating.apply_deferred_prior (w, last_t[w], t, eta, rho, n_items);
             last_t[w] = t;
            }

          for (unsigned int f = 0; f < n_features; ++f)
            {
              classifier.apply_deferred_prior (f,
                                               last_feature_t[f],
                                               t,
                                               eta,
                                               rho);
              last_feature_t[f] = t;
            }

          if (! quiet)
            {
              fprintf (stderr, " finished\n");
            }
        }
    }

  void
  usage (const char* strategy)
    {
      if (strategy && 
          (strcmp (strategy, "nominal") == 0 ||
           strcmp (strategy, "ordinal") == 0))
        {
          std::cerr << "usage: playerpiano --strategy " << strategy << " <arguments>" << std::endl
                    << "" << std::endl
                    << "  --quiet: quiet (don't display progress)." << std::endl
                    << "" << std::endl
                    << "  --test_only: output predictions only, do not update model." << std::endl
                    << "" << std::endl
                    << "  --initial_t [arg]: specify initial time for learning rate decay. (default: 1000)" << std::endl
                    << "" << std::endl
                    << "  --eta [arg]: specify initial learning rate. (default: 1.0)" << std::endl
                    << "" << std::endl
                    << "  --rho [arg]: specify learning rate decay exponent. (default: 0.5)" << std::endl
                    << "" << std::endl
                    << "  --n_items [arg]: specify effective number of items." << std::endl
                    << "" << std::endl
                    << "  --n_labels [arg]: specify number of labels." << std::endl
                    << "" << std::endl
                    << "  --n_worker_bits [arg]: specify log_2 effective number of workers.  (default: 16)" << std::endl
                    << "" << std::endl
                    << "  --n_feature_bits [arg]: specify log_2 effective number of features.  (default: 16)" << std::endl
                    << "" << std::endl
                    << "  --model [arg]: specify model file name." << std::endl
                    << "" << std::endl
                    << "  --data [arg]: specify file to read input data from." << std::endl
                    << "      if datafile eq '-' use standard input." << std::endl
                    << "      if not specified, use standard input." << std::endl
                    << "" << std::endl
                    << "  --predict [arg]: specify file to write predictions to." << std::endl
                    << "      if not specified, do not write predictions." << std::endl
                    << "" << std::endl;
        }
      else
        {
          std::cerr << "usage: playerpiano <arguments>" << std::endl
                    << "" << std::endl
                    << "  --help: display this message" << std::endl
                    << "" << std::endl
                    << "  --strategy [nominal|ordinal]" << std::endl
                    << "    select type of problem to solve." << std::endl
                    << "    combine with --help to see strategy" << std::endl
                    << "    specific argument explanations." << std::endl;
        }
    }

  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            T                                   arg_specs,
            Model*                              model);

  template<>
  bool
  ignoring (std::map<std::string, const char*>&,
            Null,
            Model*)
    {
      return false;
    }

  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            ArgSpec<T>                          arg_specs,
            Model*                              model)
    {
      if (! model->lookup (arg_specs.name).first)
        {
          std::cerr << "ERROR: model is missing parameter '"
                    << arg_specs.name
                    << "'"
                    << std::endl;

          return true;
        }

      if ((args.find ("--quiet") == args.end ()) &&
          args[arg_specs.name] && 
          strcmp (static_cast<char*> (model->lookup (arg_specs.name).first),
                  args[arg_specs.name]) != 0)
        {
          std::cerr 
            << "WARNING: overriding command line specification '"
            << args[arg_specs.name]
            << "' for argument '"
            << arg_specs.name
            << "' using model file value '"
            << static_cast<char*> (model->lookup (arg_specs.name).first)
            << "'"
            << std::endl;
        }

      return false;
    }

  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            DefaultValue<T>                     arg_specs,
            Model*                              model)
    {
      return ignoring (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  bool
  ignoring (std::map<std::string, const char*>& args,
            std::pair<Head, Tail>               arg_specs,
            Model*                              model)
    {
      return ignoring (args, arg_specs.first, model) || 
             ignoring (args, arg_specs.second, model);
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          T                                   arg_specs,
          Model*                              model);

  template<>
  void
  ensure (std::map<std::string, const char*>&,
          Null,
          Model*)
    {
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          ArgSpec<T>                          arg_specs,
          Model*                              model)
    {
      model->ensure (arg_specs.name, 1 + strlen (args[arg_specs.name]));
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          DefaultValue<T>                     arg_specs,
          Model*                              model)
    {
      ensure (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  void
  ensure (std::map<std::string, const char*>& args,
          std::pair<Head, Tail>               arg_specs,
          Model*                              model)
    {
      ensure (args, arg_specs.first, model);
      ensure (args, arg_specs.second, model);
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           T                                   arg_specs,
           Model*                              model);

  template<>
  void
  persist (std::map<std::string, const char*>&,
           Null,
           Model*)
    {
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           ArgSpec<T>                          arg_specs,
           Model*                              model)
    {
      strcpy (static_cast<char*> (model->lookup (arg_specs.name).first),
              args[arg_specs.name]);
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           DefaultValue<T>                     arg_specs,
           Model*                              model)
    {
      persist (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  void
  persist (std::map<std::string, const char*>& args,
           std::pair<Head, Tail>               arg_specs,
           Model*                              model)
    {
      persist (args, arg_specs.first, model);
      persist (args, arg_specs.second, model);
    }

  template<typename T>
  Model*
  get_model (std::map<std::string, const char*>& args,
             T                                   arg_specs)
    {
      bool test_only = (args.find ("--test_only") != args.end ());
      struct stat buf;
      bool exists = (stat (args["--model"], &buf) == 0);

      if (! exists)
        {
          if (required (args, arg_specs))
            {
              return 0;
            }
        }

      Model* model = 
        new Model (Model::Open (args["--model"]).read_only (test_only));

      if (model)
        {
          if (exists)
            {
              if (ignoring (args, arg_specs, model))
                {
                  return 0;
                }
            }
          else
            {
              ensure (args, arg_specs, model);
              persist (args, arg_specs, model);
            }
        }

      return model;
    }

  Model*
  get_model (std::map<std::string, const char*>& args)
    {
      if (required (args,
                    ArgSpec<NonEmptyString> ("--model",
                                             NonEmptyString ())))
        {
          return 0;
        }

      struct stat buf;
      bool exists = (stat (args["--model"], &buf) == 0);
      const char* strategy;
      std::auto_ptr<Model> model;

      if (exists)
        {
          const char* pathname = args["--model"];
          model = std::auto_ptr<Model> (new Model (Model::Open (pathname).read_only (true)));
          strategy = static_cast<const char*> (model->lookup ("--strategy").first);

          if (! strategy)
            {
              std::cerr << "ERROR: invalid model file '"
                        << args["--model"]
                        << "': --strategy value not found in file"
                        << std::endl;
              return 0;
            }
        }
      else
        {
          if (required (args,
                        ArgSpec<NonEmptyString> ("--strategy",
                                                 NonEmptyString ())))
            {
              return 0;
            }

          strategy = args["--strategy"];
        }

      if (strcmp (strategy, "nominal") == 0)
        {
          return
            get_model (args,
                       arg_list ()
                        .add ("--strategy", ConstantString ("nominal"))
                        .add ("--n_items", PositiveInteger (1))
                        .add ("--n_labels", PositiveInteger (2))
                        .add ("--n_worker_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_feature_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .value);
        }
      else if (strcmp (strategy, "ordinal") == 0)
        {
          return
            get_model (args,
                       arg_list ()
                        .add ("--strategy", ConstantString ("ordinal"))
                        .add ("--n_items", PositiveInteger (1))
                        .add ("--n_labels", PositiveInteger (2))
                        .add ("--n_worker_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_feature_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .value);
        }
      else
        {
          std::cerr << "ERROR: unrecognized strategy '" 
                    << args["--strategy"] 
                    << "'" 
                    << std::endl;
          std::cerr << "try --help" << std::endl;
          return 0;
        }
    }

  void
  print_gamma (const float* gamma,
               unsigned int n_labels)
  { 
    fprintf (stderr,
             "\n"
             "gamma:\n"
             "     \\  ground truth\n"
             "      |");
  
    for (unsigned int n = 0; n < n_labels; ++n)
      { 
        fprintf (stderr, "\t%3d", n);
      }
  
    fprintf (stderr,
             "\n"
             "label |\n");
  
    for (unsigned int k = 0; k < n_labels; ++k)
      { 
        fprintf (stderr,
               "%5d |",
                 k);
  
        for (unsigned int j = 0; j < n_labels; ++j)
          { 
            fprintf (stderr,
                     "\t%4.4f",
                     (k == j) ? -1.0 :
                     gamma[j * n_labels + k]);
          }
  
        fprintf (stderr,
             "\n");
      }
  }

  static void
  print_gamma_ordinal (const float* gamma,
                       unsigned int n_labels)
  {
    fprintf (stderr, "gamma =");

    for (unsigned int n = 0; n + 1 < n_labels; ++n)
      {
        fprintf (stderr, " %4.4f", gamma[n] + n + 0.5);
      }

    fprintf (stderr, "\n");
  }
}

int
main (int   argc,
      char* argv[])
{
  std::set<std::string> takes_arg;

  takes_arg.insert ("--initial_t");
  takes_arg.insert ("--eta");
  takes_arg.insert ("--rho");
  takes_arg.insert ("--n_items");
  takes_arg.insert ("--n_labels");
  takes_arg.insert ("--n_worker_bits");
  takes_arg.insert ("--n_feature_bits");
  takes_arg.insert ("--model");
  takes_arg.insert ("--data");
  takes_arg.insert ("--predict");
  takes_arg.insert ("--strategy");

  std::map<std::string, const char*> args = playerpiano::cl_parse (argc,
                                                                   argv,
                                                                   takes_arg);

  if (args.find ("-h") != args.end () ||
      args.find ("--help") != args.end () ||
      argc == 1)
    {
      usage (args["--strategy"]);
      return 1;
    }

  Model* model = get_model (args);

  if (! model)
    {
      std::cerr << "ERROR: can't open model file '"
                << args["--model"]
                << "'"
                << std::endl;
      return 1;
    }

  required (args,
            arg_list ()
             .add ("--initial_t", PositiveInteger (1))
             .default_value ("1000")
             .add ("--eta", RealNumber (0))
             .default_value ("1.0")
             .add ("--rho", RealNumber (0, 0.99))
             .default_value ("0.5")
             .value);

  if (strcmp (static_cast<char*> (model->lookup ("--strategy").first),
              "nominal") == 0)
    {
      LogisticRegression::ensure (model);
      Nominal::ensure (model);

      std::ifstream input;
      LogisticRegression logistic (model);
      Nominal nominal (model);
      NormalDistribution priorlogbeta (0, 1);
      bool test_only = (args.find ("--test_only") != args.end ());
      bool quiet = (args.find ("--quiet") != args.end ());
      FILE* predict = 
        args["--predict"] ? 
        strcmp (args["--predict"], "-") == 0 ? stdout 
                                             : fopen (args["--predict"], "w")
                          : 0;
      FILE* in = 
        args["--data"] ? 
        strcmp (args["--data"], "-") == 0 ? stdin
                                          : fopen (args["--data"], "r")
                          : stdin;

      if (! quiet)
        {
          std::cerr << "strategy = " << static_cast<char*> (model->lookup ("--strategy").first) << std::endl
                    << "initial_t = " << args["--initial_t"] << std::endl
                    << "eta = " << args["--eta"] << std::endl
                    << "rho = " << args["--rho"] << std::endl
                    << "n_items = " << static_cast<char*> (model->lookup ("--n_items").first) << std::endl
                    << "n_labels = " << static_cast<char*> (model->lookup ("--n_labels").first) << std::endl
                    << "n_worker_bits = " << static_cast<char*> (model->lookup ("--n_worker_bits").first) << std::endl
                    << "n_feature_bits = " << static_cast<char*> (model->lookup ("--n_feature_bits").first) << std::endl
                    << "test_only = " << (test_only ? "true" : "false") << std::endl
                    << "prediction file = " << (args["--predict"] ? strcmp (args["--predict"], "-") == 0 ? "(stdout)" : args["--predict"] : "(no output)") << std::endl
                    << "data file = " << (args["--data"] ? strcmp (args["--data"], "-") == 0 ? "(stdin)" : args["--data"] : "(stdin)") << std::endl;
        }

      read_loop (in,
                 strtoul (args["--initial_t"], 0, 0),
                 strtof (args["--eta"], 0),
                 strtof (args["--rho"], 0),
                 1U << strtoul (static_cast<char*> (model->lookup ("--n_worker_bits").first), 0, 0),
                 nominal,
                 strtoul (static_cast<char*> (model->lookup ("--n_items").first), 0, 0),
                 strtoul (static_cast<char*> (model->lookup ("--n_labels").first), 0, 0),
                 priorlogbeta,
                 1U << strtoul (static_cast<char*> (model->lookup ("--n_feature_bits").first), 0, 0),
                 logistic,
                 test_only,
                 predict,
                 quiet);

      if (! quiet && ! test_only)
        {
          print_gamma (static_cast<float*> (model->lookup ("gamma").first),
                       strtoul (static_cast<char*> (model->lookup ("--n_labels").first), 0, 0));
        }

      if (predict && predict != stdout)
        {
          fclose (predict);
        }
    }
  else if (strcmp (static_cast<char*> (model->lookup ("--strategy").first),
                   "ordinal") == 0)
    {
      OrdinalLogisticRegression::ensure (model);
      Ordinal::ensure (model);

      std::ifstream input;
      OrdinalLogisticRegression ordinal_logistic (model);
      Ordinal ordinal (model);
      NormalDistribution priorlogbeta (0, 1);
      bool test_only = (args.find ("--test_only") != args.end ());
      bool quiet = (args.find ("--quiet") != args.end ());
      FILE* predict = 
        args["--predict"] ? 
        strcmp (args["--predict"], "-") == 0 ? stdout 
                                             : fopen (args["--predict"], "w")
                          : 0;
      FILE* in = 
        args["--data"] ? 
        strcmp (args["--data"], "-") == 0 ? stdin
                                          : fopen (args["--data"], "r")
                          : stdin;

      if (! quiet)
        {
          std::cerr << "strategy = " << static_cast<char*> (model->lookup ("--strategy").first) << std::endl
                    << "initial_t = " << args["--initial_t"] << std::endl
                    << "eta = " << args["--eta"] << std::endl
                    << "rho = " << args["--rho"] << std::endl
                    << "n_items = " << static_cast<char*> (model->lookup ("--n_items").first) << std::endl
                    << "n_labels = " << static_cast<char*> (model->lookup ("--n_labels").first) << std::endl
                    << "n_worker_bits = " << static_cast<char*> (model->lookup ("--n_worker_bits").first) << std::endl
                    << "n_feature_bits = " << static_cast<char*> (model->lookup ("--n_feature_bits").first) << std::endl
                    << "test_only = " << (test_only ? "true" : "false") << std::endl
                    << "prediction file = " << (args["--predict"] ? strcmp (args["--predict"], "-") == 0 ? "(stdout)" : args["--predict"] : "(no output)") << std::endl
                    << "data file = " << (args["--data"] ? strcmp (args["--data"], "-") == 0 ? "(stdin)" : args["--data"] : "(stdin)") << std::endl;
        }

      read_loop (in,
                 strtoul (args["--initial_t"], 0, 0),
                 strtof (args["--eta"], 0),
                 strtof (args["--rho"], 0),
                 1U << strtoul (static_cast<char*> (model->lookup ("--n_worker_bits").first), 0, 0),
                 ordinal,
                 strtoul (static_cast<char*> (model->lookup ("--n_items").first), 0, 0),
                 strtoul (static_cast<char*> (model->lookup ("--n_labels").first), 0, 0),
                 priorlogbeta,
                 1U << strtoul (static_cast<char*> (model->lookup ("--n_feature_bits").first), 0, 0),
                 ordinal_logistic,
                 test_only,
                 predict,
                 quiet);

      if (! quiet && ! test_only)
        {
          print_gamma_ordinal 
            (static_cast<float*> (model->lookup ("gamma").first),
             strtoul (static_cast<char*> (model->lookup ("--n_labels").first), 0, 0));
        }

      if (predict && predict != stdout)
        {
          fclose (predict);
        }
    }


  delete model;

  return 0;
}
